/**
 * Date: 2022/1/8
 * Author: january
 * Description: 
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <libecc/utils/log.h>
#include <libecc/libsig.h>

#define STREQU(s1, s2) (strcmp(s1, s2) == 0)

#define MODE_GENKEY "genkey"
#define MODE_EXPORT_KEY "exportkey"
#define KEY_TYPE_SM2 "sm2"
#define KEY_TYPE_RAW "raw"
#define DEFAULT_PUBKEY_NAME "pubkey"
#define DEFAULT_PRIVAKTEKEY_NAME "privatekey"

static int gen_sm2_keypair(char* privatekey_path, char*pubkey_path) {
    int ret = -1;
    ec_params ec;
    ec_key_pair keypair;
    uint8_t privatekey_buf[128];
    int privatekey_len = 32;
    uint8_t pubkey_buf[128];
    int pubkey_len = 64;
    FILE* privatekey_file = NULL;
    FILE* pubkey_file = NULL;

    if (privatekey_path == NULL
        || pubkey_path == NULL) {
        LOG_ERROR("No path specified");
        goto out;
    }

    import_params(&ec, ec_get_curve_params_by_type(SM2P256V1));
    ret = ec_key_pair_gen(&keypair, &ec, SM2);
    if (ret < 0) {
        LOG_ERROR("Fail to gen keypair");
        goto out;
    }

    ret = ec_priv_key_export_to_buf(&(keypair.priv_key), privatekey_buf, privatekey_len);
    if (ret < 0) {
        LOG_ERROR("Fail to export private key");
        goto out;
    }

    ret = ec_pub_key_export_to_aff_buf(&(keypair.pub_key), pubkey_buf, pubkey_len);
    if (ret < 0) {
        LOG_ERROR("Fail to export public key");
        goto out;
    }

    privatekey_file = fopen(privatekey_path, "wb");
    if (privatekey_file == NULL) {
        LOG_ERROR("Fail to open file %s, err: %s", privatekey_file, strerror(errno));
        goto out;
    }

    ret = fwrite(privatekey_buf, 1, privatekey_len, privatekey_file);
    if (ret != privatekey_len) {
        LOG_ERROR("Fail to write file %s, err: %s", privatekey_file, strerror(errno));
        goto out;
    }

    pubkey_file = fopen(pubkey_path, "wb");
    if (pubkey_file == NULL) {
        LOG_ERROR("Fail to open file %s, err: %s", pubkey_path, strerror(errno));
        goto out;
    }

    ret = fwrite(pubkey_buf, 1, pubkey_len, pubkey_file);
    if (ret != pubkey_len) {
        LOG_ERROR("Fail to write file %s, err: %s", pubkey_path, strerror(errno));
        goto out;
    }

    ret = 0;
    out:
    if (privatekey_file != NULL) {
        fclose(privatekey_file);
    }
    if (pubkey_file != NULL) {
        fclose(pubkey_file);
    }
    return ret;
}

static int export_sm2_pubkey(char* privatekey_path, char* pubkey_path) {
    int ret = -1;
    FILE* private_key_file = NULL;
    FILE* pubkey_file = NULL;
    ec_params ec;
    ec_key_pair keypair;
    //TODO: 根据密钥长度调整缓冲区大小
    uint8_t buf[128];
    int privatekey_len = 32;
    int pubkey_len = 64;

    if (privatekey_path == NULL
        || pubkey_path == NULL) {
        LOG_ERROR("No path specified");
        goto out;
    }

    private_key_file = fopen(privatekey_path, "rb");
    if (private_key_file == NULL) {
        LOG_ERROR("Fail to open file %s, err: %s", private_key_file, strerror(errno));
        goto out;
    }

    ret = fread(buf, 1, privatekey_len, private_key_file);
    if (ret != privatekey_len) {
        LOG_ERROR("Fail to write file %s, err: %s", pubkey_path, strerror(errno));
        goto out;
    }

    import_params(&ec, ec_get_curve_params_by_type(SM2P256V1));
    ret = ec_key_pair_import_from_priv_key_buf(&keypair, &ec, buf, privatekey_len, SM2);
    if (ret < 0) {
        LOG_ERROR("Fail to export pubkey");
        goto out;
    }

    ret = ec_pub_key_export_to_aff_buf(&(keypair.pub_key), buf, pubkey_len);
    if (ret < 0) {
        LOG_ERROR("Fail to export pubkey");
        goto out;
    }

    pubkey_file = fopen(pubkey_path, "wb");
    if (pubkey_file == NULL) {
        LOG_ERROR("Fail to open file %s, err: %s", pubkey_path, strerror(errno));
        goto out;
    }

    ret = fwrite(buf, 1, pubkey_len, pubkey_file);
    if (ret != pubkey_len) {
        LOG_ERROR("Fail to write file %s, err: %s", pubkey_path, strerror(errno));
        goto out;
    }

    ret = -1;
    out:
    if (private_key_file != NULL) {
        fclose(private_key_file);
    }
    if (pubkey_file != NULL) {
        fclose(pubkey_file);
    }
    return ret;
}

static int genkey(char* key_type, char* out_filename) {
    int ret = -1;
    char privkey_filename_buf[256];
    char pubkey_filename_buf[256];

    if (key_type == NULL) {
        LOG_ERROR("No key type specified");
        goto out;
    }
    snprintf(privkey_filename_buf, 255, "%s.key", out_filename);
    snprintf(pubkey_filename_buf, 255, "%s.pub", out_filename);

    if (STREQU(KEY_TYPE_SM2, key_type)) {
        return gen_sm2_keypair(privkey_filename_buf, pubkey_filename_buf);
    } else {
        LOG_ERROR("Unsupport key type %s", key_type);
    }
    ret = 0;
    out:
    return ret;
}

static int exportkey(char* key_type, char* org_key_path, char* out_filename) {
    int ret = -1;
    char pubkey_filename_buf[256];

    if (key_type == NULL) {
        LOG_ERROR("No key type specified");
        goto out;
    }
    snprintf(pubkey_filename_buf, 255, "%s.pub", out_filename);
    if (STREQU(KEY_TYPE_SM2, key_type)) {
        return export_sm2_pubkey(org_key_path, pubkey_filename_buf);
    } else {
        LOG_ERROR("Unsupport key type %s", key_type);
    }
    ret = 0;
    out:
    return ret;
}

static void show_help() {
    const char *help = \
    "keytool --type <key type>\n" \
    "        --mode [genkey | exportkey]\n" \
    "        --keylen <keylen> 密钥长度（比特)\n" \
    "        --in <input filename> 输入文件路径\n" \
    "        --test <test name> 执行制定测试\n" \
    "        --out <key name> 输出密钥名称";
    NOTICE("%s", help);
}
static void load_subpubkey(const char* filename) {
    int ret;
    ec_params ec;
    ec_key_pair keypair;
    uint8_t buf[256];
    uint8_t der[256];
    int keyfile_len;
    FILE* f;

    import_params(&ec, ec_get_curve_params_by_type(SM2P256V1));
    f = fopen(filename, "rb");
    keyfile_len = fread(buf, 1, 256, f);
    keyfile_len = pem2der(buf, keyfile_len, der, 256);
    ret = sm2_import_subpubkey(&(keypair.pub_key), &ec, der, keyfile_len);
    if (ret >= 0) {
        NOTICE("success to load subpubkey");
    } else {
        NOTICE("Fail to load subpubkey");
    }
    fclose(f);
}

static void load_sec1prvkey(const char* filename) {
    int ret;
    ec_params ec;
    ec_key_pair keypair;
    uint8_t buf[256];
    uint8_t der[256];
    int keyfile_len;
    FILE* f;

    import_params(&ec, ec_get_curve_params_by_type(SM2P256V1));
    f = fopen(filename, "rb");
    keyfile_len = fread(buf, 1, 256, f);
    keyfile_len = pem2der(buf, keyfile_len, der, 256);
    ret = sm2_import_sec1privkey(&keypair, &ec, der, keyfile_len);
    if (ret >= 0) {
        NOTICE("success to load sec1privkey");
    } else {
        NOTICE("Fail to load sec1privkey");
    }
    fclose(f);
}

static void convert_openssl_sig(const char* filename) {
    int ret = -1;
    FILE* f;
    uint8_t org[128];
    uint8_t out[128];
    f = fopen(filename, "rb");
    ret = fread(org, 1, 128, f);
    ret = sm2_convert_openssl_sig(org, ret, out, 128);
    if (ret < 0) {
        LOG_ERROR("Fail to convert data");
    } else {
        LOG_INFO_MEM(out, ret, "out data(%d):", ret);
    }
    fclose(f);
}

static void convert_openssl_enc(const char* filename) {
    int ret = -1;
    FILE* f;
    uint8_t org[128];
    uint8_t out[128];
    f = fopen(filename, "rb");
    ret = fread(org, 1, 128, f);
    ret = sm2_convert_openssl_enc(org, ret, out, 128);
    if (ret < 0) {
        LOG_ERROR("Fail to convert data");
    } else {
        LOG_INFO_MEM(out, ret, "out data(%d):", ret);
    }
    fclose(f);
}

int main(int argc, char* argv[]) {
    int ret = -1;
    char* out_name = NULL;
    char* key_type = NULL;
    char* mode = NULL;
    char* in_filename = NULL;
    char* test_name = NULL;
    int key_len_bit;
    int key_len;
    int option_index;
    int c;

    while (1) {
        static struct option long_options[] = {
                {"type", required_argument, 0,  0},
                {"mode", required_argument, 0, 1},
                {"keylen", required_argument, 0, 2},
                {"in", required_argument, 0, 3},
                {"out", required_argument, 0, 4},
                {"test", required_argument, 0, 5},
                {"help", no_argument, 0, 6},
                {0,  0,  0,  0 }
        };
        c = getopt_long(argc, argv, "h",
                        long_options, &option_index);
        if (c == -1)
            break;
        switch (c) {
            case 0:
                if (STREQU(KEY_TYPE_SM2, optarg)) {
                    key_type = KEY_TYPE_SM2;
                } else if(STREQU(KEY_TYPE_RAW, optarg)) {
                    key_type = KEY_TYPE_RAW;
                } else {
                    LOG_ERROR("Not support key type %s", optarg);
                    goto out;
                }
                break;
            case 1:
                if (STREQU(MODE_GENKEY, optarg)) {
                    mode = MODE_GENKEY;
                } else if (STREQU(MODE_EXPORT_KEY, optarg)) {
                    mode = MODE_EXPORT_KEY;
                } else {
                    LOG_ERROR("Not support %s", optarg);
                    goto out;
                }
                break;
            case 2:
                key_len = atoi(optarg);
                if (key_len < 128) {
                    LOG_ERROR("Invalid key len %d", key_len);
                    goto out;
                }
                break;
            case 3: in_filename = optarg; break;
            case 4: out_name = optarg; break;
            case 5: test_name = optarg; break;
            case 'h':
            case 6: show_help(); goto out;
            case '?': LOG_WARNING("Unknown option, %s", optarg); break;
            default: LOG_WARNING("?? getopt returned character code 0%o ??\n", c);
        }
    }

    // 功能测试使用
    if (test_name != NULL) {
        if (STREQU(test_name, "subpubkey")) {
            load_subpubkey(in_filename);
        } else if(STREQU(test_name, "sec1")) {
            load_sec1prvkey(in_filename);
        } else if (STREQU(test_name, "sig_convert")) {
            convert_openssl_sig(in_filename);
        } else if(STREQU(test_name, "enc_convert")) {
            convert_openssl_enc(in_filename);
        } else {
            LOG_ERROR("No such test: %s", test_name);
        }
        goto out;
    }

    if (out_name == NULL) {
        out_name = key_type;
    }

    if (mode == NULL) {
        show_help();
    } else if (STREQU(MODE_GENKEY, mode)) {
        genkey(key_type, out_name);
    } else if(STREQU(MODE_EXPORT_KEY, mode)) {
        exportkey(key_type, in_filename, out_name);
    } else {
        show_help();
    }
    ret = 0;
    out:
    return ret;
}